import sdl2
import sdl2.sdlimage
import zengl
import numpy as np
from math import pi, sin, cos, tau
from pyrr import Matrix44, Quaternion, Vector3
import struct, ctypes

def V3(x, y, z):
    return Vector3([x, y, z], dtype="f4")

def U3(x, y, z):
    return Vector3([x, z, -y], dtype="f4")

def US3(x, y, z):   # scale vector
    return Vector3([x, z, y], dtype="f4")


def VD3(x, y, z):       # double
    return Vector3([x, y, z])

def UD3(x, y, z):
    return Vector3([x, z, -y])

def length(v):
    return np.linalg.norm(v)

def ev(v):
    return v / norm(v)

U3.fd = U3(0, 1, 0)
U3.bk = U3(0, -1, 0)
U3.rt = U3(1, 0, 0)
U3.lt = U3(-1, 0, 0)
U3.up = U3(0, 0, 1)
U3.dn = U3(0, 0, -1)
U3.c1 = U3(1, 1, 1)
U3.c2 = U3(-1, 1, 1)
U3.c3 = U3(-1, -1, 1)
U3.c4 = U3(1, -1, 1)
U3.c5 = U3(1, 1, -1)
U3.c6 = U3(-1, 1, -1)
U3.c7 = U3(-1, -1, -1)
U3.c8 = U3(1, -1, -1)

vrot = Quaternion.from_axis_rotation




class Turtle:
    def __init__(self, pos=None, rot=None, parent=None):
        if rot is None:
            self.rot = Quaternion()
        else:
            self.rot = rot
        if pos is None:
            self.pos = V3(0, 0, 0)
        else:
            self.pos = pos
        self.parent = parent


    def copy(self):
        new = Turtle()
        new.rot = self.rot.copy()
        new.pos = self.pos.copy()
        return new


    def __getitem__(self, thing):
        if isinstance(thing, np.ndarray) and thing.shape == (3,):
            t = self.abs()
            x, y, z = v3
            return t.rot * V3(x, y ,z) + t.pos
        else:
            l = []
            for x, y, z in v3l:
                l.append(t.rot * V3(x, y ,z) + t.pos)
            return np.array(l)


    def abs(self):
        if self.parent is None:
            return self.copy()
        else:
            r = self.parent.abs().iadd(self.pos, self.rot)
            return r

    def connect(self, he):
        self.parent = he
        pos, rot = self.iadd_inv2(he)
        self.pos = pos
        self.rot = rot

    def disconnect(self):
        t = self.abs()
        self.parent = None
        self.pos = t.pos
        self.rot = t.rot


    def set(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        self.pos = pos
        self.rot = rot

    def walk(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        self.pos += pos
        self.rot = rot * self.rot

    def iwalk(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        self.pos += self.rot * pos
        self.rot = self.rot * rot

    def add(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        return Turtle(self.pos + pos, rot * self.rot)

    def iadd(self, pos=None, rot=None):
        if rot is None:
            rot = Quaternion()
        if pos is None:
            pos = V3(0, 0, 0)
        return Turtle(self.pos + self.rot * pos, self.rot * rot)

    def iadd_inv(self, t1):
        rot0 = self.rot * t1.rot.conjugate
        pos0 = self.pos - rot0 * t1.pos
        return Turtle(pos0, rot0)

    def iadd_inv2(self, t0):
        rot = t0.rot.conjugate * self.rot
        pos = t0.rot.conjugate * (self.pos - t0.pos)
        return pos, rot

    @property
    def rt(self):
        return self.rot * U3.rt

    @property
    def up(self):
        return self.rot * U3.up

    @property
    def fd(self):
        return self.rot * U3.fd

    @property
    def lt(self):
        return self.rot * U3.lt

    @property
    def dn(self):
        return self.rot * U3.dn

    @property
    def bk(self):
        return self.rot * U3.bk

    def pile(self, obj, anchor):
        assert isinstance(obj, Obj)
        obj.pile(self, anchor)


class Box:
    # 记录盒子中心的坐标
    # 坐标是相对于宿主的坐标
    def __init__(self, size, master):
        x, y, z = size
        self.size = V3(abs(x), abs(y), abs(z))
        self.pos = U3(0, 0, 0)
        self.rot = Quaternion()
        assert type(master) is Obj
        self.master = master

    def anchor(self, v):
        a = v * self.size / 2
        t = self.master.t.abs().add(self.pos, self.rot)
        # ~ return t.iadd_inv(-a, Quaternion())
        return t.iadd(a)
        # ~ return Turtle(t.pos + self.master.rot * a, t.rot)

    def copy(self, o):
        b = Box(self.size, o)
        b.pos = self.pos
        b.rot = self.rot
        return b



class Drawable:
    pass

class Moveable:
    pass


class Camera(Moveable):
    def __init__(self, pos=None, rot=None, fog=60, xy=10/8):
        self.proj = Matrix44.perspective_projection(fog, xy, 0.1, 500.0).astype("f4")
        if pos is None:
            pos = V3(0, 1, 20)
        if rot is None:
            rot = Quaternion()
        self.t = Turtle(pos, rot)

    def p(self):
        return self.proj

    def v(self):
        return (self.t.rot.matrix44 *  Matrix44.from_translation(-self.t.pos)).astype('f4')


prog1 = {
    "vertex_shader":'''
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

out vec3 wPos;
out vec3 vPos;
out vec3 wNormal;
out vec3 vNormal;

void main() {
    vec4 wPos4 = modelMat * vec4(aPos, 1.0);
    vec4 vPos4 = viewMat * wPos4;
    wPos = wPos4.xyz;
    vPos = vPos4.xyz;
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    gl_Position = projMat * vPos4;
  }
            ''',
    "fragment_shader":'''
#version 330 core
precision highp float;

in vec3 wPos;
in vec3 vPos;
in vec3 wNormal;
in vec3 vNormal;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 Help;

void main()
{
    // gamma jiao4zheng4
    vec3 objectColor_line = objectColor;
    objectColor_line.r = pow(objectColor_line.r, 2);
    objectColor_line.g = pow(objectColor_line.g, 2);
    objectColor_line.b = pow(objectColor_line.b, 2);

    float ambientStrength = 0.05;
    vec3 ambient = ambientStrength * objectColor_line;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(lightPos - wPos);
    float diff = max(dot(norm, lightDir) , 0.0);
    //vec3 diffuse = diff * vec3(0.5, 0.5, 0.5) ;
    vec3 diffuse = diff * objectColor_line ;

    vec3 viewDir = normalize(viewPos - wPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);
    result.r = pow(result.r, 0.5);
    result.g = pow(result.g, 0.5);
    result.b = pow(result.b, 0.5);

    //vec3 red = vec3(1, 0, 0);

    FragColor = vec4(result, 1.0);

    //Help = vec4(normalize(vNormal) , length(vPos));
    Help = vec4(normalize(vNormal)  * length(vPos), 1);


}
            '''
}


prog1_mc = {
    "vertex_shader":'''
#version 320 es
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

out vec3 wPos;
out vec3 vPos;
out vec3 wNormal;
out vec3 vNormal;

void main() {
    vec4 wPos4 = modelMat * vec4(aPos, 1.0);
    vec4 vPos4 = viewMat * wPos4;
    wPos = wPos4.xyz;
    vPos = vPos4.xyz;
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    gl_Position = projMat * vPos4;
  }
            ''',
    "fragment_shader":'''
#version 320 es
precision highp float;

in vec3 wPos;
in vec3 vPos;
in vec3 wNormal;
in vec3 vNormal;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 Help;

void main()
{
    // gamma jiao4zheng4
    vec3 objectColor_line = objectColor;
    objectColor_line.r = pow(objectColor_line.r, 2.0);
    objectColor_line.g = pow(objectColor_line.g, 2.0);
    objectColor_line.b = pow(objectColor_line.b, 2.0);

    float ambientStrength = 0.3;
    vec3 ambient = ambientStrength * objectColor_line;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(-vec3(1, -0.65, 1.6));
    float diff = max(dot(norm, lightDir) , 0.0);
    vec3 diffuse = diff * objectColor_line *0.8 ;

    lightDir = normalize(-vec3(-1, -0.65, -1.6));
    diff = max(dot(norm, lightDir) , 0.0);
    diffuse = diffuse + diff * objectColor_line *0.8 ;

    vec3 viewDir = normalize(viewPos - wPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);
    result.r = pow(result.r, 0.5);
    result.g = pow(result.g, 0.5);
    result.b = pow(result.b, 0.5);

    //vec3 red = vec3(1, 0, 0);

    FragColor = vec4(result, 1.0);

    //Help = vec4(normalize(vNormal) , length(vPos));
    Help = vec4(normalize(vNormal)  * length(vPos), 1);


}
            '''
}




class Obj(Drawable, Moveable):
    def __init__(self):
        self.vertices = []
        self.normals = []
        self.color = V3(0.7, 0.7, 0.7)
        self.prog = prog1

        self.t = Turtle()
        self.box = None

        self.pipeline = None
        self.uniform_buffer = None

    def u(self, window):       # update
        assert len(self.vertices) != 0
        assert hasattr(window, "render")
        assert hasattr(window, "update")
        assert hasattr(window, "mainloop")

        vl = np.array(self.vertices, dtype="f4")
        nl = np.array(self.normals, dtype="f4")
        vn = np.dstack([vl[:, 0], vl[:, 1], vl[:, 2], nl[:, 0], nl[:, 1], nl[:, 2]])
        buf = window.ctx.buffer(vn.tobytes())
        try:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, dynamic=True)
        except:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, uniform=True)
        self.pipeline = window.ctx.pipeline(
            vertex_shader = self.prog["vertex_shader"],
            fragment_shader = self.prog["fragment_shader"],
            framebuffer = [window.img, window.imgh, window.imgs],
            layout=[
                {'name': 'Common', 'binding': 0},
            ],
            resources=[
                {
                    'type': 'uniform_buffer',
                    'binding': 0,
                    'buffer': self.uniform_buffer,
                },
            ],
            vertex_buffers = zengl.bind(buf, "3f 3f", 0, 1),
            vertex_count = buf.size // zengl.calcsize('3f 3f'),
            # ~ blend = {'enable': True},
        )



    def copy(self):
        o = Obj()
        o.vertices = self.vertices.copy()
        o.normals = self.normals.copy()
        o.color = self.color
        o.prog = self.prog

        o.t = self.t.copy()
        if self.box is not None:
            o.box = self.box.copy(o)

        o.pipeline = self.pipeline
        o.uniform_buffer = self.uniform_buffer
        return o

    def as_unit(self):
        v = np.array(self.vertices) @ np.array(self.t.abs().rot.matrix33).T + self.t.abs().pos
        n = np.array(self.normals) @ np.array(self.t.abs().rot.matrix33).T
        return v, n

    def __rrshift__(self, obj):
        if len(obj) != 2:
            raise Exception(f"add part faild, {obj}")
        else:
            v, n = obj
            assert len(v) == len(n)
            self.vertices.extend(v)
            self.normals.extend(n)

    def m(self):
        t = self.t.abs()
        return ( Matrix44.from_translation(t.pos) * t.rot.matrix44.T).astype("f4")

    def draw(self, camera):
        if self.pipeline is None:
            print("没准备好绘制", self)
            return

        m = self.m()
        v = camera.v()
        p = camera.p()
        pos = camera.t.pos.astype("f4")

        self.uniform_buffer.write(struct.pack('64s64s64s3f1f3f1f3f1f', m.tobytes(), v.tobytes(), p.tobytes(), *pos, 0, *pos, 0, *self.color, 0))
        self.pipeline.render()

    def pile(self, t, anchor:"U(-1~1, -1~1, -1~1)"):
        a = anchor * self.box.size / 2
        t = t.abs()
        self.t.pos = t.pos - t.rot * a
        self.t.rot= t.rot


prog100 = {
    "vertex_shader":'''
#version 320 es
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

layout (std140) uniform Ins {
    vec4[100] pos_list;
};


out vec3 wPos;
out vec3 vPos;
out vec3 wNormal;
out vec3 vNormal;

void main() {
    vec4 move = pos_list[gl_InstanceID];
    mat4 movem = mat4(
    1, 0 ,0, 0,
    0, 1, 0 ,0,
    0, 0, 1, 0,
    move.x, move.y ,move.z, 1
    );
    vec4 wPos4 = movem * modelMat * vec4(aPos * move.w, 1.0);
    vec4 vPos4 = viewMat * wPos4;
    wPos = wPos4.xyz;
    vPos = vPos4.xyz;
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    gl_Position = projMat * vPos4;
  }
            ''',
    "fragment_shader":'''
#version 320 es
precision highp float;

in vec3 wPos;
in vec3 vPos;
in vec3 wNormal;
in vec3 vNormal;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 Help;

void main()
{
    // gamma jiao4zheng4
    vec3 objectColor_line = objectColor;
    objectColor_line.r = pow(objectColor_line.r, 2.0);
    objectColor_line.g = pow(objectColor_line.g, 2.0);
    objectColor_line.b = pow(objectColor_line.b, 2.0);

    float ambientStrength = 0.05;
    vec3 ambient = ambientStrength * objectColor_line;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(-vec3(1, -0.65, 1.6));
    float diff = max(dot(norm, lightDir) , 0.0);
    vec3 diffuse = diff * objectColor_line *0.8 ;

    lightDir = normalize(-vec3(-1, -0.65, -1.6));
    diff = max(dot(norm, lightDir) , 0.0);
    diffuse = diffuse + diff * objectColor_line *0.8 ;

    vec3 viewDir = normalize(viewPos - wPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);
    result.r = pow(result.r, 0.5);
    result.g = pow(result.g, 0.5);
    result.b = pow(result.b, 0.5);

    //vec3 red = vec3(1, 0, 0);

    FragColor = vec4(result, 1.0);

    //Help = vec4(normalize(vNormal) , length(vPos));
    Help = vec4(normalize(vNormal)  * length(vPos), 1);


}
            '''
}


class Obj100(Drawable, Moveable):
    def __init__(self):
        self.vertices = []
        self.normals = []
        self.color = V3(0.7, 0.7, 0.7)
        self.prog = prog100

        self.t = Turtle()
        self.box = None

        self.pipeline = None
        self.uniform_buffer = None
        self.storage_buffer = None
        self.pos_list = [[U3(0, 0, 0), 1]]


    def u(self, window):       # update
        assert len(self.vertices) != 0
        assert hasattr(window, "render")
        assert hasattr(window, "update")
        assert hasattr(window, "mainloop")

        vl = np.array(self.vertices, dtype="f4")
        nl = np.array(self.normals, dtype="f4")
        vn = np.dstack([vl[:, 0], vl[:, 1], vl[:, 2], nl[:, 0], nl[:, 1], nl[:, 2]])
        buf = window.ctx.buffer(vn.tobytes())
        try:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, dynamic=True)
        except:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, uniform=True)
        self.storage_buffer = window.ctx.buffer(size=1600)
        self.pipeline = window.ctx.pipeline(
            vertex_shader = self.prog["vertex_shader"],
            fragment_shader = self.prog["fragment_shader"],
            framebuffer = [window.img, window.imgh, window.imgs],
            layout=[
                {'name': 'Common', 'binding': 0},
                {'name': 'Ins', 'binding': 1},
            ],
            resources=[
                {
                    'type': 'uniform_buffer',
                    'binding': 0,
                    'buffer': self.uniform_buffer,
                },
                {
                    'type': 'uniform_buffer',
                    'binding': 1,
                    'buffer': self.storage_buffer,
                },
            ],
            vertex_buffers = zengl.bind(buf, "3f 3f", 0, 1),
            vertex_count = buf.size // zengl.calcsize('3f 3f'),
            # ~ blend = {'enable': True},
        )
        length = len(self.pos_list)
        self.pipeline.instance_count = length
        arr1, arr2 = zip(*self.pos_list)
        arr1 = np.array(arr1)
        arr2 = np.array(arr2).reshape((length, 1))
        arr = np.hstack([arr1, arr2])
        self.storage_buffer.write(arr.astype("f4").tobytes())

    def update100(self, l):
        self.pos_list = l.copy()
        length = len(self.pos_list)
        self.pipeline.instance_count = length
        arr1, arr2 = zip(*self.pos_list)
        arr1 = np.array(arr1)
        arr2 = np.array(arr2).reshape((length, 1))
        arr = np.hstack([arr1, arr2])
        assert arr.shape[1] == 4, arr.shape

        # ~ print(arr.shape)
        self.storage_buffer.write(arr.astype("f4").tobytes())


    def copy(self):
        o = Obj()
        o.vertices = self.vertices.copy()
        o.normals = self.normals.copy()
        o.color = self.color
        o.prog = self.prog

        o.t = self.t.copy()
        if self.box is not None:
            o.box = self.box.copy(o)

        o.pipeline = self.pipeline
        o.uniform_buffer = self.uniform_buffer
        return o

    def as_unit(self):
        v = np.array(self.vertices) @ np.array(self.t.abs().rot.matrix33).T + self.t.abs().pos
        n = np.array(self.normals) @ np.array(self.t.abs().rot.matrix33).T
        return v, n

    def __rrshift__(self, obj):
        if len(obj) != 2:
            raise Exception(f"add part faild, {obj}")
        else:
            v, n = obj
            assert len(v) == len(n)
            self.vertices.extend(v)
            self.normals.extend(n)

    def m(self):
        t = self.t.abs()
        return ( Matrix44.from_translation(t.pos) * t.rot.matrix44.T).astype("f4")

    def draw(self, camera):
        if self.pipeline is None:
            print("没准备好绘制", self)
            return

        m = self.m()
        v = camera.v()
        p = camera.p()
        pos = camera.t.pos.astype("f4")

        self.uniform_buffer.write(struct.pack('64s64s64s3f1f3f1f3f1f', m.tobytes(), v.tobytes(), p.tobytes(), *pos, 0, *pos, 0, *self.color, 0))
        self.pipeline.render()

    def pile(self, t, anchor:"U(-1~1, -1~1, -1~1)"):
        a = anchor * self.box.size / 2
        t = t.abs()
        self.t.pos = t.pos - t.rot * a
        self.t.rot= t.rot

progt1 = {
    "vertex_shader":'''
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTex;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

out vec3 wPos;
out vec3 vPos;
out vec3 wNormal;
out vec3 vNormal;
out vec2 texcoord;

void main() {
    vec4 wPos4 = modelMat * vec4(aPos, 1.0);
    vec4 vPos4 = viewMat * wPos4;
    wPos = wPos4.xyz;
    vPos = vPos4.xyz;
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    gl_Position = projMat * vPos4;
    texcoord = aTex;
  }
            ''',
    "fragment_shader":'''
#version 330 core
precision highp float;

in vec3 wPos;
in vec3 vPos;
in vec3 wNormal;
in vec3 vNormal;
in vec2 texcoord;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

uniform sampler2D texture0;

layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 Help;

void main()
{
    // gamma jiao4zheng4
    vec4 colora = texture2D(texture0, texcoord);
    vec3 objectColor_line = colora.rgb;
    //vec3 objectColor_line = texture2D(texture0, texcoord).xyz * 0 + vec3(texcoord.x, texcoord.y, 0);
    objectColor_line.r = pow(objectColor_line.r, 2);
    objectColor_line.g = pow(objectColor_line.g, 2);
    objectColor_line.b = pow(objectColor_line.b, 2);

    float ambientStrength = 0.05;
    vec3 ambient = ambientStrength * objectColor_line;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(lightPos - wPos);
    float diff = max(dot(norm, lightDir) , 0.0);
    //vec3 diffuse = diff * vec3(0.5, 0.5, 0.5) ;
    vec3 diffuse = diff * objectColor_line ;

    vec3 viewDir = normalize(viewPos - wPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);
    result.r = pow(result.r, 0.5);
    result.g = pow(result.g, 0.5);
    result.b = pow(result.b, 0.5);

    vec3 red = vec3(1, 0, 0);

    FragColor = vec4(result, colora.a);
    //FragColor = vec4(result * 0 + red, 0.2);

    //Help = vec4(normalize(vNormal) , length(vPos));
    Help = vec4(normalize(vNormal)  * length(vPos), 1);


}
            '''
}


class Objt(Drawable, Moveable):
    def __init__(self, texture):
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.color = V3(0.7, 0.7, 0.7)
        self.prog = progt1


        self.t = Turtle()
        self.box = None

        self.pipeline = None
        self.uniform_buffer = None
        assert isinstance(texture, zengl.Image)
        self.texture = texture

    def u(self, window):       # update
        assert len(self.vertices) != 0
        assert hasattr(window, "render")
        assert hasattr(window, "update")
        assert hasattr(window, "mainloop")

        vl = np.array(self.vertices, dtype="f4")
        nl = np.array(self.normals, dtype="f4")
        tl = np.array(self.texcoords, dtype="f4")
        # ~ print(tl.shape)
        vn = np.dstack([vl[:, 0], vl[:, 1], vl[:, 2], nl[:, 0], nl[:, 1], nl[:, 2], tl[:, 0], tl[:, 1]])
        buf = window.ctx.buffer(vn.tobytes())
        try:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, dynamic=True)
        except:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, uniform=True)
        self.pipeline = window.ctx.pipeline(
            vertex_shader = self.prog["vertex_shader"],
            fragment_shader = self.prog["fragment_shader"],
            framebuffer = [window.img, window.imgh, window.imgs],
            layout=[
                {'name': 'Common', 'binding': 0},
                {'name': 'texture0', 'binding': 0},
            ],
            resources=[
                {
                    'type': 'uniform_buffer',
                    'binding': 0,
                    'buffer': self.uniform_buffer,
                },
                {
                    'type': 'sampler',
                    'binding': 0,
                    'image': self.texture,
                },
            ],
            vertex_buffers = zengl.bind(buf, "3f 3f 2f", 0, 1, 2),
            vertex_count = buf.size // zengl.calcsize('3f 3f 2f'),
            blend = {
                'enable': True,
                'src_color': 'src_alpha',
                'dst_color': 'one_minus_src_alpha',
                'src_alpha': 'one',
                'dst_alpha': 'one_minus_src_alpha',
            },
        )



    def copy(self):
        o = Obj()
        o.vertices = self.vertices.copy()
        o.normals = self.normals.copy()
        o.color = self.color
        o.prog = self.prog

        o.t = self.t.copy()
        if self.box is not None:
            o.box = self.box.copy(o)

        o.pipeline = self.pipeline
        o.uniform_buffer = self.uniform_buffer
        return o

    def as_unit(self):
        v = np.array(self.vertices) @ np.array(self.t.abs().rot.matrix33).T + self.t.abs().pos
        n = np.array(self.normals) @ np.array(self.t.abs().rot.matrix33).T
        return v, n

    def __rrshift__(self, obj):
        if len(obj) != 2:
            raise Exception(f"add part faild, {obj}")
        else:
            v, n = obj
            assert len(v) == len(n)
            self.vertices.extend(v)
            self.normals.extend(n)

    def m(self):
        t = self.t.abs()
        return ( Matrix44.from_translation(t.pos) * t.rot.matrix44.T).astype("f4")

    def draw(self, camera):
        if self.pipeline is None:
            print("没准备好绘制", self)
            return

        m = self.m()
        v = camera.v()
        p = camera.p()
        pos = camera.t.pos.astype("f4")

        self.uniform_buffer.write(struct.pack('64s64s64s3f1f3f1f3f1f', m.tobytes(), v.tobytes(), p.tobytes(), *pos, 0, *pos, 0, *self.color, 0))
        self.pipeline.render()

    def pile(self, t, anchor:"U(-1~1, -1~1, -1~1)"):
        a = anchor * self.box.size / 2
        t = t.abs()
        self.t.pos = t.pos - t.rot * a
        self.t.rot= t.rot


progt100 = {
    "vertex_shader":'''
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTex;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

layout (std140) uniform Ins {
    vec4[100] pos_list;
};


out vec3 wPos;
out vec3 vPos;
out vec3 wNormal;
out vec3 vNormal;
out vec2 texcoord;

void main() {
    vec4 move = pos_list[gl_InstanceID];
    mat4 movem = mat4(
    1, 0 ,0, 0,
    0, 1, 0 ,0,
    0, 0, 1, 0,
    move.x, move.y ,move.z, 1
    );
    vec4 wPos4 = movem * modelMat * vec4(aPos, 1.0);
    vec4 vPos4 = viewMat * wPos4;
    wPos = wPos4.xyz;
    vPos = vPos4.xyz;
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    gl_Position = projMat * vPos4;
    texcoord = aTex;
  }
            ''',
    "fragment_shader":'''
#version 330 core
precision highp float;

in vec3 wPos;
in vec3 vPos;
in vec3 wNormal;
in vec3 vNormal;
in vec2 texcoord;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

uniform sampler2D texture0;

layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 Help;

void main()
{
    // gamma jiao4zheng4
    vec4 colora = texture2D(texture0, texcoord);
    vec3 objectColor_line = colora.rgb;
    //vec3 objectColor_line = texture2D(texture0, texcoord).xyz * 0 + vec3(texcoord.x, texcoord.y, 0);
    objectColor_line.r = pow(objectColor_line.r, 2);
    objectColor_line.g = pow(objectColor_line.g, 2);
    objectColor_line.b = pow(objectColor_line.b, 2);

    float ambientStrength = 0.05;
    vec3 ambient = ambientStrength * objectColor_line;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(lightPos - wPos);
    float diff = max(dot(norm, lightDir) , 0.0);
    //vec3 diffuse = diff * vec3(0.5, 0.5, 0.5) ;
    vec3 diffuse = diff * objectColor_line ;

    vec3 viewDir = normalize(viewPos - wPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);
    result.r = pow(result.r, 0.5);
    result.g = pow(result.g, 0.5);
    result.b = pow(result.b, 0.5);

    vec3 red = vec3(1, 0, 0);

    FragColor = vec4(result, colora.a);
    //FragColor = vec4(result * 0 + red, 0.2);

    //Help = vec4(normalize(vNormal) , length(vPos));
    Help = vec4(normalize(vNormal)  * length(vPos), 1);


}
            '''
}


class Objt100(Drawable, Moveable):
    def __init__(self, texture):
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.color = V3(0.7, 0.7, 0.7)
        self.prog = progt100



        self.t = Turtle()
        self.box = None

        self.pipeline = None
        self.uniform_buffer = None
        self.storage_buffer = None
        self.pos_list = [U3(0, 0, 0)]
        assert isinstance(texture, zengl.Image)
        self.texture = texture

    def u(self, window):       # update
        assert len(self.vertices) != 0
        assert hasattr(window, "render")
        assert hasattr(window, "update")
        assert hasattr(window, "mainloop")

        vl = np.array(self.vertices, dtype="f4")
        nl = np.array(self.normals, dtype="f4")
        tl = np.array(self.texcoords, dtype="f4")
        # ~ print(tl.shape)
        vn = np.dstack([vl[:, 0], vl[:, 1], vl[:, 2], nl[:, 0], nl[:, 1], nl[:, 2], tl[:, 0], tl[:, 1]])
        buf = window.ctx.buffer(vn.tobytes())
        try:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, dynamic=True)
        except:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, uniform=True)
        self.storage_buffer = window.ctx.buffer(size=1600)
        self.pipeline = window.ctx.pipeline(
            vertex_shader = self.prog["vertex_shader"],
            fragment_shader = self.prog["fragment_shader"],
            framebuffer = [window.img, window.imgh, window.imgs],
            layout=[
                {'name': 'Common', 'binding': 0},
                {'name': 'texture0', 'binding': 0},
                {'name': 'Ins', 'binding': 1},
            ],
            resources=[
                {
                    'type': 'uniform_buffer',
                    'binding': 0,
                    'buffer': self.uniform_buffer,
                },
                {
                    'type': 'sampler',
                    'binding': 0,
                    'image': self.texture,
                },
                {
                    'type': 'uniform_buffer',
                    'binding': 1,
                    'buffer': self.storage_buffer,
                },
            ],
            vertex_buffers = zengl.bind(buf, "3f 3f 2f", 0, 1, 2),
            vertex_count = buf.size // zengl.calcsize('3f 3f 2f'),
            blend = {
                'enable': True,
                'src_color': 'src_alpha',
                'dst_color': 'one_minus_src_alpha',
                'src_alpha': 'one',
                'dst_alpha': 'one_minus_src_alpha',
            },
        )

    def update100(self, l):
        self.pos_list = l.copy()
        self.pipeline.instance_count = len(self.pos_list)
        arr = np.array(self.pos_list)
        assert arr.shape[1] == 3, arr.shape
        arr = np.hstack([arr, np.zeros((len(self.pos_list), 1))])
        # ~ print(arr.shape)
        self.storage_buffer.write(arr.astype("f4").tobytes())


    def copy(self):
        o = Objt100()
        o.vertices = self.vertices.copy()
        o.normals = self.normals.copy()
        o.color = self.color
        o.prog = self.prog

        o.t = self.t.copy()
        if self.box is not None:
            o.box = self.box.copy(o)

        o.pipeline = self.pipeline
        o.uniform_buffer = self.uniform_buffer
        return o

    def as_unit(self):
        v = np.array(self.vertices) @ np.array(self.t.abs().rot.matrix33).T + self.t.abs().pos
        n = np.array(self.normals) @ np.array(self.t.abs().rot.matrix33).T
        return v, n

    def __rrshift__(self, obj):
        if len(obj) != 2:
            raise Exception(f"add part faild, {obj}")
        else:
            v, n = obj
            assert len(v) == len(n)
            self.vertices.extend(v)
            self.normals.extend(n)

    def m(self):
        t = self.t.abs()
        return ( Matrix44.from_translation(t.pos) * t.rot.matrix44.T).astype("f4")

    def draw(self, camera):
        if self.pipeline is None:
            print("没准备好绘制", self)
            return

        m = self.m()
        v = camera.v()
        p = camera.p()
        pos = camera.t.pos.astype("f4")

        self.uniform_buffer.write(struct.pack('64s64s64s3f1f3f1f3f1f', m.tobytes(), v.tobytes(), p.tobytes(), *pos, 0, *pos, 0, *self.color, 0))
        self.pipeline.render()

    def pile(self, t, anchor:"U(-1~1, -1~1, -1~1)"):
        a = anchor * self.box.size / 2
        t = t.abs()
        self.t.pos = t.pos - t.rot * a
        self.t.rot= t.rot



ads100 = {
    "vertex_shader":'''
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTex;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};

struct Pp {
    vec4 pos;
    vec4 piece;
};

layout (std140) uniform Ins {
    Pp[100] pp_list;
};


out vec3 wPos;
out vec3 vPos;
out vec3 wNormal;
out vec3 vNormal;
out vec2 texcoord;

void main() {
    vec4 move = pp_list[gl_InstanceID].pos;
    mat4 movem = mat4(
    1, 0 ,0, 0,
    0, 1, 0 ,0,
    0, 0, 1, 0,
    move.x, move.y ,move.z, 1
    );
    vec4 wPos4 = movem * modelMat * vec4(aPos, 1.0);
    vec4 vPos4 = viewMat * wPos4;
    wPos = wPos4.xyz;
    vPos = vPos4.xyz;
    wNormal = mat3(modelMat) * aNormal;
    vNormal = mat3(viewMat * modelMat) * aNormal;
    gl_Position = projMat * vPos4;
    texcoord = aTex * pp_list[gl_InstanceID].piece.zw + pp_list[gl_InstanceID].piece.xy;
  }
            ''',
    "fragment_shader":'''
#version 330 core
precision highp float;

in vec3 wPos;
in vec3 vPos;
in vec3 wNormal;
in vec3 vNormal;
in vec2 texcoord;

layout (std140) uniform Common {
    mat4 modelMat;
    mat4 viewMat;
    mat4 projMat;
    vec3 lightPos;
    float n1;
    vec3 viewPos;
    float n2;
    vec3 objectColor;
    float n3;
};



uniform sampler2D texture0;

layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 Help;

void main()
{
    // gamma jiao4zheng4

    vec4 colora = texture2D(texture0, texcoord);
    vec3 objectColor_line = colora.rgb;
    //vec3 objectColor_line = texture2D(texture0, texcoord).xyz * 0 + vec3(texcoord.x, texcoord.y, 0);
    objectColor_line.r = pow(objectColor_line.r, 2);
    objectColor_line.g = pow(objectColor_line.g, 2);
    objectColor_line.b = pow(objectColor_line.b, 2);

    float ambientStrength = 0.05;
    vec3 ambient = ambientStrength * objectColor_line;

    vec3 norm = normalize(wNormal);
    vec3 lightDir = normalize(lightPos - wPos);
    float diff = max(dot(norm, lightDir) , 0.0);
    //vec3 diffuse = diff * vec3(0.5, 0.5, 0.5) ;
    vec3 diffuse = diff * objectColor_line ;

    vec3 viewDir = normalize(viewPos - wPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(wNormal, halfwayDir), 0.0), 32.0);
    vec3 specular = spec * vec3(0.3);

    vec3 result = (ambient + diffuse + specular * 0.1);
    result.r = pow(result.r, 0.5);
    result.g = pow(result.g, 0.5);
    result.b = pow(result.b, 0.5);

    vec3 red = vec3(1, 0, 0);

    //FragColor = vec4(result, colora.a);
    FragColor = vec4(result * 0 + colora.xyz, colora.a);

    //Help = vec4(normalize(vNormal) , length(vPos));
    //Help = vec4(normalize(vNormal)  * length(vPos), 0);
    Help = vec4(normalize(vNormal)  * length(vPos), 1);
    //Help = vec4(normalize(vNormal)  * length(vPos), colora.a);


}
            '''
}


class Ads100(Drawable, Moveable):
    def __init__(self, texture):
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.color = V3(0.7, 0.7, 0.7)
        self.prog = ads100



        self.t = Turtle()
        self.box = None

        self.pipeline = None
        self.uniform_buffer = None
        self.storage_buffer = None
        self.pos_list = [U3(0, 0, 0)]
        self.piece_list = [np.array([0, 0, 1, 1])]
        assert isinstance(texture, zengl.Image)
        self.texture = texture

    def u(self, window):       # update
        assert len(self.vertices) != 0
        assert hasattr(window, "render")
        assert hasattr(window, "update")
        assert hasattr(window, "mainloop")

        vl = np.array(self.vertices, dtype="f4")
        nl = np.array(self.normals, dtype="f4")
        tl = np.array(self.texcoords, dtype="f4")
        # ~ print(tl.shape)
        vn = np.dstack([vl[:, 0], vl[:, 1], vl[:, 2], nl[:, 0], nl[:, 1], nl[:, 2], tl[:, 0], tl[:, 1]])
        buf = window.ctx.buffer(vn.tobytes())
        try:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, dynamic=True)
        except:
            self.uniform_buffer = window.ctx.buffer(size=64+64+64+16+16+16, uniform=True)
        self.storage_buffer = window.ctx.buffer(size=3200)
        self.pipeline = window.ctx.pipeline(
            vertex_shader = self.prog["vertex_shader"],
            fragment_shader = self.prog["fragment_shader"],
            framebuffer = [window.img, window.imgh, window.imgs],
            layout=[
                {'name': 'Common', 'binding': 0},
                {'name': 'texture0', 'binding': 0},
                {'name': 'Ins', 'binding': 1},
            ],
            resources=[
                {
                    'type': 'uniform_buffer',
                    'binding': 0,
                    'buffer': self.uniform_buffer,
                },
                {
                    'type': 'sampler',
                    'binding': 0,
                    'image': self.texture,
                },
                {
                    'type': 'uniform_buffer',
                    'binding': 1,
                    'buffer': self.storage_buffer,
                },
            ],
            vertex_buffers = zengl.bind(buf, "3f 3f 2f", 0, 1, 2),
            vertex_count = buf.size // zengl.calcsize('3f 3f 2f'),
            blend = {
                'enable': True,
                'src_color': 'src_alpha',
                'dst_color': 'one_minus_src_alpha',
                'src_alpha': 'one',
                'dst_alpha': 'one_minus_src_alpha',
            },
        )

    def update100(self, l1, l2):
        assert len(l1) == len(l2)
        self.pos_list = l1.copy()
        self.piece_list = l2.copy()
        print("here", self.pipeline.instance_count)
        self.pipeline.instance_count = len(self.pos_list)
        arr = np.array(self.pos_list)
        assert arr.shape[1] == 3, arr.shape
        arr2 = np.array(self.piece_list)
        assert arr2.shape[1] == 4, arr2.shape
        arr = np.hstack([arr, np.zeros((len(self.pos_list), 1)), arr2])
        self.storage_buffer.write(arr.astype("f4").tobytes())


    def copy(self):
        o = Objt100()
        o.vertices = self.vertices.copy()
        o.normals = self.normals.copy()
        o.color = self.color
        o.prog = self.prog

        o.t = self.t.copy()
        if self.box is not None:
            o.box = self.box.copy(o)

        o.pipeline = self.pipeline
        o.uniform_buffer = self.uniform_buffer
        return o

    def as_unit(self):
        v = np.array(self.vertices) @ np.array(self.t.abs().rot.matrix33).T + self.t.abs().pos
        n = np.array(self.normals) @ np.array(self.t.abs().rot.matrix33).T
        return v, n

    def __rrshift__(self, obj):
        if len(obj) != 2:
            raise Exception(f"add part faild, {obj}")
        else:
            v, n = obj
            assert len(v) == len(n)
            self.vertices.extend(v)
            self.normals.extend(n)

    def m(self):
        t = self.t.abs()
        return ( Matrix44.from_translation(t.pos) * t.rot.matrix44.T).astype("f4")

    def draw(self, camera):
        if self.pipeline is None:
            print("没准备好绘制", self)
            return

        m = self.m()
        v = camera.v()
        p = camera.p()
        pos = camera.t.pos.astype("f4")

        self.uniform_buffer.write(struct.pack('64s64s64s3f1f3f1f3f1f', m.tobytes(), v.tobytes(), p.tobytes(), *pos, 0, *pos, 0, *self.color, 0))
        self.pipeline.render()

    def pile(self, t, anchor:"U(-1~1, -1~1, -1~1)"):
        a = anchor * self.box.size / 2
        t = t.abs()
        self.t.pos = t.pos - t.rot * a
        self.t.rot= t.rot








'''
def make_ring(l1, l2, r=False):     # 默认第二个环在第一个环的正方向上
    assert len(l1) * len(l2) != 0
    if len(l1) == len(l2) == 1: assert False
    elif len(l1) == 1:
        a = l1[0]
        if r == False:
            l = []
            for i in range(len(l2)):
                l.extend([a, l2[i], l2[i-1]])
        else:
            l = []
            for i in range(len(l2)):
                l.extend([a, l2[i-1], l2[i]])
        return l
    elif len(l2) == 1:
        return make_ring(l2, l1, r=True)
    else:
        assert len(l1) == len(l2)
        if r == False:
            la = []
            for i in range(len(l2)):
                la.extend([l1[i-1], l2[i], l2[i-1]])
            lb = []
            for i in range(len(l2)):
                lb.extend([l1[i-1], l1[i], l2[i]])
        else:
            la = []
            for i in range(len(l2)):
                la.extend([l1[i-1], l2[i-1], l2[i]])
            lb = []
            for i in range(len(l2)):
                lb.extend([l1[i], l1[i-1], l2[i-1]])
        return la + lb
'''

def make_block(size=U3(0,0,0)):
    t = Turtle()
    x, z, y = size
    x, z, y = abs(x), abs(z), abs(y)
    hx, hz, hy = size / 2
    hx, hz, hy = abs(hx), abs(hz), abs(hy)
    l = np.array([U3(hx, hy, 0), U3(-hx, hy, 0), U3(-hx, -hy, 0), U3(hx, -hy, 0)])
    a, b, c, d = l1 = l - U3(0, 0, hz)
    e, f, g, h = l2 = l + U3(0, 0, hz)
    v = []
    v.extend([a, b, f])
    v.extend([b, c, g])
    v.extend([c, d, h])
    v.extend([d, a, e])
    v.extend([a, f, e])
    v.extend([b, g, f])
    v.extend([c, h, g])
    v.extend([d, e, h])
    v.extend([a, c, b])
    v.extend([a, d, c])
    v.extend([e, f, g])
    v.extend([e, g, h])
    o = Obj()
    o.vertices = v
    o.normals = easy_norm(v)
    o.box = Box(U3(x, y, z), o)
    return o

def make_tri(p1, p2 , p3):
    v = []
    v.extend([p1, p2, p3])
    v.extend([p1, p3, p2])
    o = Obj()
    o.vertices = v
    o.normals = easy_norm(v)
    return o

def easy_norm(l):
    assert len(l) % 3 == 0
    l2 = []
    for i in range(len(l))[::3]:
        a,b,c = l[i], l[i+1], l[i+2]
        a = np.array(a)
        b = np.array(b)
        c = np.array(c)
        x = np.cross(b - a, c - b)
        # ~ x = np.cross(c - b, b - a)
        m = np.linalg.norm(x)
        x = x / m
        l2.append(x)
        l2.append(x)
        l2.append(x)
    return l2

def lookat(p, up=None):
    if up is None:
        up = U3(0, 0, 1)
    m = Matrix44.look_at(U3(0, 0, 0), p, up)
    return Quaternion.from_matrix(m)



def mapf(l):
    def g(f):
        return [f(a) for a in l]
    return g


def circle(type, d, n):
    r = d / 2
    th = pi * 2 / n
    if type == "xy":
       l = [U3(r * cos(th * i), r * sin(th * i), 0) for i in range(n)]
    elif type == "xz":
        l = [U3(r * cos(th * i), 0, r * sin(th * i)) for i in range(n)]
    elif type == "yz":
        l = [U3(0, r * cos(th * i), r * sin(th * i)) for i in range(n)]
    elif type == "-xy":
       l = [U3(-r * cos(th * i), r * sin(th * i), 0) for i in range(n)]
    elif type == "-xz":
        l = [U3(-r * cos(th * i), 0, r * sin(th * i)) for i in range(n)]
    elif type == "-yz": #左视图
        l = [U3(0, -r * cos(th * i), r * sin(th * i)) for i in range(n)]
    else:
        assert False
    return l


def rect(type, a, b):
    a = a / 2
    b = b / 2
    if type == "xy":
        l = [U3(a, b, 0), U3(-a, b, 0), U3(-a, -b, 0), U3(a, -b, 0)]
    elif type == "xz":
        l = [U3(a, 0, b), U3(-a, 0, b), U3(-a, 0, -b), U3(a, 0, -b)]
    elif type == "yz":
        l = [U3(0, a, b), U3(0, -a, b), U3(0, -a, -b), U3(0, a, -b)]
    elif type == "-xy":
        l = [U3(-a, b, 0), U3(a, b, 0), U3(a, -b, 0), U3(-a, -b, 0)]
    elif type == "-xz":
        l = [U3(-a, 0, b), U3(a, 0, b), U3(a, 0, -b), U3(-a, 0, -b)]
    elif type == "-yz":     # 左视图
        l = [U3(0, -a, b), U3(0, a, b), U3(0, a, -b), U3(0, -a, -b)]
    else:
        assert False
    return np.array(l)

def make_circle(type, d, n):
    c = circle(type, d, n)
    l = []
    l = make_ring([U3(0, 0, 0)], c) + make_ring(c, [U3(0, 0, 0)])
    o = Obj()
    o.vertices = l
    o.normals = easy_norm(l)
    return o

def make_quad(type, a, b):
    c = rect(type, a, b)
    l = []
    l.extend([c[0], c[1], c[2]])
    l.extend([c[0], c[2], c[3]])
    l.extend([c[0], c[2], c[1]])
    l.extend([c[0], c[3], c[2]])
    o = Obj()
    o.vertices = l
    o.normals = easy_norm(l)
    return o



def create_screen_quad():
    p1 = V3(-1, 1, 0)
    p2 = V3(-1, -1, 0)
    p3 = V3(1, -1, 0)
    p4 = V3(1, 1, 0)
    qv = np.array([p1, p2, p3, p1, p3, p4], dtype=np.float32)
    uv1 = [0.0, 0.0]
    uv2 = [0.0, 1.0]
    uv3 = [1.0, 1.0]
    uv4 = [1.0, 0.0]
    quv = np.array([uv1, uv2, uv3, uv1, uv3, uv4, ], dtype=np.float32)
    arr = np.dstack([qv[:, 0], qv[:, 1], qv[:, 2], quv[:, 0], quv[:, 1]]).astype("f4")
    return arr

prog_render2d = {
    "vertex_shader": '''
#version 330 core

layout (location = 0) in vec3 in_position;
layout (location = 1) in vec2 in_texcoord_0;

layout (std140) uniform Common {
    mat4 all_mat;
};

out vec2 uv;

void main() {
    gl_Position = all_mat * vec4(in_position, 1.0);
    uv = vec2(in_texcoord_0.x, 1 - in_texcoord_0.y);
}
 ''',
    "fragment_shader": '''
#version 330 core
layout (location = 0) out vec4 fragColor;

uniform sampler2D texture0;

in vec2 uv;

void main()
{
    vec4 rgba = texture(texture0, uv);
    fragColor = vec4(rgba.rgb, rgba.a);
    //fragColor = vec4(rgba.a, 0, 0, 1);
}

            '''
}

class Render2d(Drawable):
    def __init__(self):
        self.pipeline  = None
        self.uniform_buffer = None
        self.texture = None


    def u(self, window, sdlsurf=False):
        assert hasattr(window, "render")
        assert hasattr(window, "update")
        assert hasattr(window, "mainloop")

        try:
            self.uniform_buffer = window.ctx.buffer(size=64, dynamic=True)
        except:
            self.uniform_buffer = window.ctx.buffer(size=64, uniform=True)
        self.texture = window.ctx.image(window.size, 'rgba8unorm')

        arr = create_screen_quad()
        buf = window.ctx.buffer(arr.tobytes())
        self.pipeline = window.ctx.pipeline(
            vertex_shader = prog_render2d["vertex_shader"] if not sdlsurf else prog_render2d["vertex_shader"].replace("1 - in_texcoord_0.y", "in_texcoord_0.y"),
            fragment_shader = prog_render2d["fragment_shader"],
            framebuffer = [window.img, window.imgs],
            layout=[
                {'name': 'Common', 'binding': 0},
                {'name': 'texture0', 'binding': 0},
            ],
            resources=[
                {
                    'type': 'uniform_buffer',
                    'binding': 0,
                    'buffer': self.uniform_buffer,
                },
                {
                    'type': 'sampler',
                    'binding': 0,
                    'image': self.texture,
                },
            ],
            vertex_buffers = zengl.bind(buf, "3f 2f", 0, 1),
            vertex_count = buf.size // zengl.calcsize('3f 2f'),
            blend = {
                'enable': True,
                'src_color': 'src_alpha',
                'dst_color': 'one_minus_src_alpha',
                'src_alpha': 'one',
                'dst_alpha': 'one_minus_src_alpha',
            },
        )

        v = Quaternion().matrix44 *  Matrix44.from_translation(-U3(0, -10, 0))
        p = Matrix44.orthogonal_projection(-1, 1, -1, 1, 0, window.size[0])
        am = (p * v).astype("f4")
        self.uniform_buffer.write(struct.pack('64s', am.tobytes()))

    def draw(self):
        self.pipeline.render()


def load_texture(ctx, name):
    # 加载图像文件
    surface = sdl2.sdlimage.IMG_Load(name.encode('utf-8'))
    # 获取图像宽高
    try:
        width = surface.contents.w
        height = surface.contents.h
    except Exception:
        print(sdl2.SDL_GetError())
        exit()

    # 获取像素数据
    pitch = surface.contents.pitch
    pixels_ptr = surface.contents.pixels
    print(width, height, pitch, pixels_ptr)
    # 使用 ctypes 读取像素数据
    pixel_data = ctypes.string_at(pixels_ptr, pitch * height)

    # 创建 Zengl 图像对象
    texture = ctx.image((width, height), 'rgba8unorm', pixel_data)
    return texture

def as_image(ctx, surface):
    width = surface.w
    height = surface.h
    pitch = surface.pitch
    pixels_ptr = surface.pixels
    pixel_data = ctypes.string_at(pixels_ptr, pitch * height)
    texture = ctx.image((width, height), 'rgba8unorm', pixel_data)
    # ~ print(texture.read())
    return texture

def to_image(surface, image):
    width = surface.w
    height = surface.h
    pitch = surface.pitch
    pixels_ptr = surface.pixels
    pixel_data = ctypes.string_at(pixels_ptr, pitch * height)
    image.write(pixel_data)
